2020 excess mortality & voting patterns in CH

Redistributed cantonal deaths

Data

Canton

Expected deaths by age X sex X canton in 2020 from Riou et al..

Contains iterations of diff results

max(exp_deaths_2020_kt$it)
[1] 1000

Example of one iteration

Data summary
Name Piped data
Number of rows 260
Number of columns 6
_______________________
Column type frequency:
character 3
numeric 3
________________________
Group variables None

Variable type: character

skim_variable n_missing complete_rate min max empty n_unique whitespace
canton 0 1 2 2 0 26 0
age_group 0 1 3 5 0 5 0
sex 0 1 4 6 0 2 0

Variable type: numeric

skim_variable n_missing complete_rate mean sd p0 p25 p50 p75 p100 hist
it 0 1 1.00 0.00 1 1.00 1.0 1.00 1 ▁▁▇▁▁
exp_deaths 0 1 216.61 399.69 0 22.75 75.5 217.25 2857 ▇▁▁▁▁
observed2 0 1 254.35 491.21 0 23.00 81.0 247.75 3891 ▇▁▁▁▁

Municipality

Data prepared in 02.Rmd. Collapsing age groups and renaming variables to match cantonal estimates.

w_deaths_2020_year_fin = read_rds("data/BfS-closed/monthly_deaths/w_deaths_2015_2020_year_fin.Rds") %>% 
  filter(year == 2020) %>% select(-year) %>% 
  rename(age_group = age,
         canton = KTNAME) %>% 
  mutate(age_group = if_else(age_group == "40-49", "40-59", age_group),
         age_group = if_else(age_group == "50-59", "40-59", age_group)) %>% 
  group_by(canton, GMDNR, age_group, sex) %>% 
  summarise(observed = sum(observed),
            pop_mid_poi = sum(pop_mid_poi)) %>% 
  ungroup()
Data summary
Name Piped data
Number of rows 21450
Number of columns 6
_______________________
Column type frequency:
character 3
numeric 3
________________________
Group variables None

Variable type: character

skim_variable n_missing complete_rate min max empty n_unique whitespace
canton 0 1 2 2 0 26 0
age_group 0 1 3 5 0 5 0
sex 0 1 4 6 0 2 0

Variable type: numeric

skim_variable n_missing complete_rate mean sd p0 p25 p50 p75 p100 hist
GMDNR 0 1 3289.82 2133.49 1 1067 3296 5411 6810 ▇▃▅▅▇
observed 0 1 3.55 18.99 0 0 1 3 1493 ▇▁▁▁▁
pop_mid_poi 0 1 403.70 1769.38 0 44 123 336 116058 ▇▁▁▁▁

Spatial

kt = read_rds("data/BfS/kt.Rds")
gg = read_rds("data/BfS/gg.Rds")
tg3o = read_rds("data/BfS/tg3o.Rds")
se_alt = read_rds("data/BfS/se_alt.Rds")

Downscale function

@jriou method to downscale cantonal deaths to municipality level.

source("R/downscale_year.R")

Downscale

exp_deaths_2020_year = downscale_year(exp_deaths_2020_kt, w_deaths_2020_year_fin)

Again, result contains iterations of diff results

max(exp_deaths_2020_year$it)
[1] 1000

Example of one iteration

Data summary
Name Piped data
Number of rows 21450
Number of columns 12
_______________________
Column type frequency:
character 3
numeric 9
________________________
Group variables None

Variable type: character

skim_variable n_missing complete_rate min max empty n_unique whitespace
canton 0 1 2 2 0 26 0
age_group 0 1 3 5 0 5 0
sex 0 1 4 6 0 2 0

Variable type: numeric

skim_variable n_missing complete_rate mean sd p0 p25 p50 p75 p100 hist
GMDNR 0 1.00 3289.82 2133.49 1 1067 3296.00 5411.00 6810 ▇▃▅▅▇
observed 0 1.00 3.55 18.99 0 0 1.00 3.00 1493 ▇▁▁▁▁
pop_mid_poi 0 1.00 403.70 1769.38 0 44 123.00 336.00 116058 ▇▁▁▁▁
it 0 1.00 1.00 0.00 1 1 1.00 1.00 1 ▁▁▇▁▁
exp_deaths 0 1.00 404.91 589.00 0 65 182.00 471.00 2857 ▇▁▁▁▁
observed2 0 1.00 467.83 712.77 0 62 173.00 547.00 3891 ▇▁▁▁▁
p 9988 0.53 0.02 0.06 0 0 0.01 0.02 1 ▇▁▁▁▁
munici_exp_deaths 0 1.00 2.63 13.31 0 0 0.00 2.00 856 ▇▁▁▁▁
munici_excess 0 1.00 0.93 6.84 -17 0 0.00 1.00 637 ▇▁▁▁▁

Preps

Aggregates

Iterations collapsed for each municipality strata.

exp_deaths_2020_year_agg = exp_deaths_2020_year %>% 
  rename(munici_observed = observed,
         cant_exp_deaths = exp_deaths,
         cant_observed = observed2) %>% 
  group_by(canton, GMDNR, age_group, sex) %>% 
  summarise(
    # observed
    munici_observed = first(munici_observed),
    population = first(pop_mid_poi),
    # modelled x1000
    # expected
    munici_expected_lo = quantile(munici_exp_deaths, 0.025),
    munici_expected_med = quantile(munici_exp_deaths, 0.5),
    munici_expected_up = quantile(munici_exp_deaths, 0.975),
    # excess
    munici_excess_lo = quantile(munici_excess, 0.025),
    munici_excess_med = quantile(munici_excess, 0.5),
    munici_excess_up = quantile(munici_excess, 0.975)
  ) %>% 
  ungroup()

Even simpler aggregation to municipality level, ignoring age & sex strata.

exp_deaths_2020_year_gem = exp_deaths_2020_year_agg %>% 
  group_by(canton, GMDNR) %>% 
  summarise(
    # community level totals
    population = sum(population),
    munici_observed = sum(munici_observed),
    munici_expected_med = sum(munici_expected_med),
    munici_excess_med = sum(munici_excess_med)
  ) %>% 
  ungroup() %>% 
  # ratio and per pop
  mutate(munici_excess_rat = munici_excess_med / munici_expected_med,
         munici_excess_pop =  (munici_excess_med / population) * 1000) 

Special situation arises in this aggregation when calculating a ratio of excess deaths to observed deaths. There are communities where expected number of deaths is zero. That leads to ratio being Inf in cases where excess is > 0 and to NaN where excess is (also) == 0.

munici_expected_med == 0 <lgl> 
# total N=2145 valid N=2145 mean=0.05 sd=0.21

Value |    N | Raw % | Valid % | Cum. %
---------------------------------------
FALSE | 2042 | 95.20 |   95.20 |  95.20
TRUE  |  103 |  4.80 |    4.80 | 100.00
<NA>  |    0 |  0.00 |    <NA> |   <NA>

These communities’ ratios were recoded to NA.

Bring covariates

exp_deaths_2020_year_agg %<>% 
  left_join(read_rds("data/BfS-closed/monthly_deaths/w_deaths_2015_2020_year_fin.Rds") %>% 
              select(GMDNR, GMDNAME, border, 
                     median_ssep3_q, r_urban1, r_urban2, r_lang) %>% 
              distinct())

exp_deaths_2020_year_gem %<>% 
  left_join(read_rds("data/BfS-closed/monthly_deaths/w_deaths_2015_2020_year_fin.Rds") %>% 
              select(GMDNR, GMDNAME, border, 
                     median_ssep3_q, r_urban1, r_urban2, r_lang) %>% 
              distinct())

Bring voting

covid_jun = read_rds("data/voting/covid_jun.Rds") %>% 
  select(GMDNR, jaStimmenInProzent, vote_yes) %>% 
  rename(vote_yes_jun_perc = jaStimmenInProzent,
         vote_yes_jun_cat = vote_yes)

covid_nov = read_rds("data/voting/covid_nov.Rds") %>% 
  select(GMDNR, jaStimmenInProzent, vote_yes) %>% 
  rename(vote_yes_nov_perc = jaStimmenInProzent,
         vote_yes_nov_cat = vote_yes)

Note: there are few small communities with no voting info - these were excluded.

# A tibble: 4 × 2
  canton GMDNAME                    
  <chr>  <chr>                      
1 BE     Meienried                  
2 BE     Hellsau                    
3 BE     Deisswil bei Münchenbuchsee
4 BE     Niedermuhlern              

Excess deaths per 1000 pop

Distribution

Maps

x <categorical> 
# total N=2141 valid N=2141 mean=3.00 sd=1.41

Value        |   N | Raw % | Valid % | Cum. %
---------------------------------------------
[0.00, 1.49) | 429 | 20.04 |   20.04 |  20.04
[1.49, 2.08) | 428 | 19.99 |   19.99 |  40.03
[2.08, 2.64) | 428 | 19.99 |   19.99 |  60.02
[2.64, 3.52) | 428 | 19.99 |   19.99 |  80.01
[3.52,27.78] | 428 | 19.99 |   19.99 | 100.00
<NA>         |   0 |  0.00 |    <NA> |   <NA>

Choropleth

Proportional symbols

Symbol size perceptually scaled to population size.

EDA June vote

Map



Correlations

Unweighted

cor.test(exp_deaths_2020_year_gem$munici_excess_pop, 
         exp_deaths_2020_year_gem$vote_yes_jun_perc, 
         method = "pearson")

    Pearson's product-moment correlation

data:  exp_deaths_2020_year_gem$munici_excess_pop and exp_deaths_2020_year_gem$vote_yes_jun_perc
t = 0.63465, df = 2139, p-value = 0.5257
alternative hypothesis: true correlation is not equal to 0
95 percent confidence interval:
 -0.02865836  0.05605123
sample estimates:
       cor 
0.01372105 

Weighted

wtd.cor(exp_deaths_2020_year_gem$munici_excess_pop, 
        exp_deaths_2020_year_gem$vote_yes_jun_perc, 
        weight = exp_deaths_2020_year_gem$munici_observed)
  correlation    std.err   t.value   p.value
Y 0.005492431 0.02188932 0.2509183 0.8019019

Scatter

Unweighted

Weighted

Box

EDA Nov vote

Map



Correlations

Unweighted

cor.test(exp_deaths_2020_year_gem$munici_excess_pop, 
         exp_deaths_2020_year_gem$vote_yes_nov_perc, 
         method = "pearson")

    Pearson's product-moment correlation

data:  exp_deaths_2020_year_gem$munici_excess_pop and exp_deaths_2020_year_gem$vote_yes_nov_perc
t = -3.6713, df = 2139, p-value = 0.0002473
alternative hypothesis: true correlation is not equal to 0
95 percent confidence interval:
 -0.12108777 -0.03689184
sample estimates:
        cor 
-0.07913093 

Weighted

wtd.cor(exp_deaths_2020_year_gem$munici_excess_pop, 
        exp_deaths_2020_year_gem$vote_yes_nov_perc, 
        weight = exp_deaths_2020_year_gem$munici_observed)
  correlation    std.err   t.value           p.value
Y   -0.130383 0.02170279 -6.007662 0.000000002214821

Scatter

Unweighted

Weighted

Box